/*-
 * Copyright (c) 2009-2010 David Forsythe.
 * See LICENSE file for license details.
 */


/*
 * pkg_freebsd.c provides convinience functions for accessing the property list
 * gernerated by parsing and writing FreeBSD Plists with freebsd_plist.c.  It
 * also provides convinience functions for creating FreeBSD package databases
 * and FreeBSD package repositories.
 */

#include <stdarg.h>
#include <archive.h>
#include <archive_entry.h>

#include "pkg.h"
#include "pkg_freebsd.h"

/* Structure for grabbing required-by strings. */
struct _pkg_reqby {
        struct pkg_property *plnode;
};

static int _pkg_freebsd_sort_conflicts(struct pkg_property *, 
        struct pkg_property *);

static int _pkg_freebsd_sort_dependencies(struct pkg_property *, 
        struct pkg_property *);

static int _pkg_freebsd_sort_files(struct pkg_property *,
        struct pkg_property *);

static int _pkg_freebsd_sort_required_by(struct pkg_property *,
        struct pkg_property *);

/* XXX: Can't be static because it's using the next macro.  Adjust the macro
 * when I get a chance. */
int _pkg_freebsd_get_next_required_by(struct pkg *, struct _pkg_reqby *);

/* XXX: need to get the pkg_client_crash stuff from base libpkg out of here. */
#define DEFINE_LIST_GET_NEXT_FUNCTION(fnname, key, type, compare)              \
int                                                                            \
fnname(struct pkg *p, struct type *x)                                          \
{                                                                              \
        struct pkg_property *plist;                                            \
        struct pkg_property *list;                                             \
        struct pkg_property *next;                                             \
        plist = pkg_get_property_list(p);                                      \
        if (plist == NULL) {                                                   \
                PKG_CLIENT_CRASH(__func__, "No property list.");               \
        }                                                                      \
        if ((list = pkg_property_dict_get_property(plist, key)) == NULL) {     \
                return (PKG_NOT_OK);                                           \
        }                                                                      \
        (void)compare;                                                         \
        next = pkg_property_list_sequence_get_next_property(list);             \
        if (next == NULL) {                                                    \
                return (PKG_NOT_OK);                                           \
        }                                                                      \
        x->plnode = next;                                                      \
        return (PKG_OK);                                                       \
}

/* Create an node in a PLLIST.  If the PLLIST does not exist, create it in the
 * packages root PLDICT. */
#define DEFINE_CREATE_IN_LIST_FUNCTION(fnname, listkey, key, type)             \
int                                                                            \
fnname(struct pkg *p, struct type *x)                                          \
{                                                                              \
        struct pkg_property *plist;                                            \
        struct pkg_property *list;                                             \
        struct pkg_property *new;                                              \
        plist = pkg_get_property_list(p);                                      \
        if (plist == NULL) {                                                   \
                return (PKG_NOT_OK);                                           \
        }                                                                      \
        if ((list = pkg_property_dict_get_property(plist, listkey)) == NULL) { \
                list = pkg_property_list_create(plist, listkey);               \
                if (list == NULL) {                                            \
                        return (PKG_NOT_OK);                                   \
                }                                                              \
        }                                                                      \
        if (pkg_property_get_type(list) != PLLIST) {                           \
                PKG_CLIENT_CRASH(__func__, "Root is not a PLLIST");            \
        }                                                                      \
        new = pkg_property_dict_create(list, key);                             \
        if (new == NULL) {                                                     \
                return (PKG_NOT_OK);                                           \
        }                                                                      \
        x->plnode = new;                                                       \
        return (PKG_OK);                                                       \
}

#define DEFINE_DICT_GET_STRING_FUNCTION(fnname, key, type)                     \
const char *                                                                   \
fnname(struct type *x)                                                         \
{                                                                              \
        struct pkg_property *pnode;                                            \
        pnode = pkg_property_dict_get_property(x->plnode, key);                \
        if (pnode == NULL) {                                                   \
                return (NULL);                                                 \
        }                                                                      \
        if (pkg_property_get_type(pnode) != PLSTRING) {                        \
                PKG_CLIENT_CRASH(__func__, "Fetched node is not a PLSTRING");  \
        }                                                                      \
        return (pkg_property_string_get_string(pnode));                        \
}


struct pkg *
pkg_freebsd_create(void)
{
        struct pkg *p;
        struct pkg_property *pl;

        p = pkg_create();
        if (p == NULL) {
                return (NULL);
        }

        pkg_set_parser_writer_vtable(p, freebsd_plist_parser_writer_vtable());
        pkg_set_meta_entry_table(p, freebsd_plist_meta_entry_table());
        
        pl = pkg_get_property_list(p);
        if (pl == NULL) {
                pkg_release(p);
                return (NULL);
        }
        
        return (p);
}

/* 
 * Create and set up a package database for interaction with a FreeBSD directory
 * database.
 */
struct pkg_database *
pkg_freebsd_database_create(void)
{
	struct pkg_database *db;
	
	db = pkg_database_create();
	if (db == NULL) {
		return (NULL);
	}
        pkg_database_directorydb_init(db);
	
	return (db);
}

struct pkg_repository *
pkg_freebsd_repository_create(void)
{
        struct pkg_repository *rp;

        rp = pkg_repository_create();
        if (rp == NULL) {
                return (NULL);
        }
        pkg_repository_ftp_init(rp);

        return (rp);
}
	
int
pkg_freebsd_set_name(struct pkg *p, const char *name)
{
        (void)p;
        (void)name;
        PKG_CLIENT_CRASH(__func__, "Not implemented.");
        return (PKG_NOT_OK);
}

const char *
pkg_freebsd_get_name(struct pkg *p)
{
        struct pkg_property *pl;
        struct pkg_property *pn;

        pl = pkg_get_property_list(p);
        if (pl == NULL) {
                return (NULL);
        }

        pn = pkg_property_dict_get_property(p->plist, FBSD_NAME);
        if (pn == NULL) {
                return (NULL);
        } /* Should probably check PL type. */

        return (pkg_property_string_get_string(pn));
}

int
pkg_freebsd_set_origin(struct pkg *p, const char *origin)
{
        (void)p;
        (void)origin;
        PKG_CLIENT_CRASH(__func__, "Not implemented.");
        return (PKG_NOT_OK);
}

const char *
pkg_freebsd_get_origin(struct pkg *p)
{
        struct pkg_property *pl;
        struct pkg_property *pn;

        pl = pkg_get_property_list(p);
        if (pl == NULL) {
                return (NULL);
        }

        pn = pkg_property_dict_get_property(pl, FBSD_ORIGIN);
        if (pn == NULL) {
                return (NULL);
        }

        return (pkg_property_string_get_string(pn));
}

const char *
pkg_freebsd_get_comment(struct pkg *p)
{
        struct pkg_property *pl;
        struct pkg_property *pn;
        const void *rval;
        ssize_t size;

        pl = pkg_get_property_list(p);
        if (pl == NULL) {
                return (NULL);
        }

        pn = pkg_property_dict_get_property(pl, FBSD_METANAME_COMMENT);
        if (pn == NULL) {
                return (NULL);
        }
        
        rval = NULL; /* Init rval. */
        size = pkg_property_data_get_data(pn, &rval);
        if (size < 0) {
                return (NULL);
        }
        
        return ((const char *)rval);
}

const char *
pkg_freebsd_get_format_revision(struct pkg *p)
{
        struct pkg_property *pl;
        struct pkg_property *pn;

        pl = pkg_get_property_list(p);
        if (pl == NULL) {
                return (NULL);
        }

        pn = pkg_property_dict_get_property(p->plist, FBSD_FORMAT_REVISION);
        if (pn == NULL) {
                return (NULL);
        } /* Should probably check PL type. */

        return (pkg_property_string_get_string(pn));
}

const char *
pkg_freebsd_get_description(struct pkg *p)
{
        struct pkg_property *pl;
        struct pkg_property *pn;
        const void *rval;
        ssize_t size;

        pl = pkg_get_property_list(p);
        if (pl == NULL) {
                return (NULL);
        }

        pn = pkg_property_dict_get_property(pl, FBSD_METANAME_DESCRIPTION);
        if (pn == NULL) {
                return (NULL);
        }
        
        rval = NULL; /* Init rval. */
        size = pkg_property_data_get_data(pn, &rval);
        if (size < 0) {
                return (NULL);
        }

        return (rval);
}

const char *
pkg_freebsd_get_display(struct pkg *p)
{
        struct pkg_property *pl;
        struct pkg_property *pn;
        const void *rval;
        ssize_t size;

        pl = pkg_get_property_list(p);
        if (pl == NULL) {
                return (NULL);
        }

        pn = pkg_property_dict_get_property(pl, FBSD_METANAME_DISPLAY);
        if (pn == NULL) {
                return (NULL);
        }
        
        rval = NULL; /* Init rval. */
        size = pkg_property_data_get_data(pn, &rval);
        if (size < 0) {
                return (NULL);
        }

        return (rval);
}

int
pkg_freebsd_get_preserve(struct pkg *p)
{
        struct pkg_property *pl;
        struct pkg_property *pn;
        
        pl = pkg_get_property_list(p);
        if (pl == NULL) {
                return (0);
        }
        pn = pkg_property_dict_get_property(pl, FBSD_PRESERVE);
        if (pn == NULL) {
                return (0);
        }
        return (pkg_property_boolean_get_boolean(pn));
}

/* Required by */

static int
_pkg_freebsd_sort_required_by(struct pkg_property *p1, struct pkg_property *p2)
{
        (void)p1;
        (void)p2;
        return (0);
}

DEFINE_LIST_GET_NEXT_FUNCTION(_pkg_freebsd_get_next_required_by,
        FBSD_REQUIRED_BY, _pkg_reqby, _pkg_freebsd_sort_required_by);

const char *
pkg_freebsd_get_next_required_by(struct pkg *p)
{
        struct _pkg_reqby rb;

        if (_pkg_freebsd_get_next_required_by(p, &rb) != PKG_OK) {
                return (NULL);
        }

        return (pkg_property_string_get_string(rb.plnode));
}

void
pkg_freebsd_required_by_rewind(struct pkg *p)
{
        struct pkg_property *pl;
        struct pkg_property *pn;
        
        pl = pkg_get_property_list(p);
        if (pl == NULL) {
                return;
        }
        if ((pn = pkg_property_dict_get_property(pl, FBSD_REQUIRED_BY)) != 
                NULL) {
                pkg_property_list_sequence_rewind(pn);
        }
}

void
pkg_freebsd_remove_required_by(struct pkg *p, const char *name)
{
        struct pkg_property *pl;
        struct pkg_property *pn;
        struct pkg_property *pp;

        pl = pkg_get_property_list(p);
        if (pl == NULL) {
                return;
        }
        pn = pkg_property_dict_get_property(pl, FBSD_REQUIRED_BY);
        if (pn == NULL) {
                return;
        }
        pp = NULL;
        while ((pp = pkg_property_list_get_next_property(pn, pp)) != NULL) {
                if (strcmp(pkg_property_string_get_string(pp), name) == 0) {
                        pkg_property_list_remove_property(pn, pp);
                        pkg_property_release(pp);
                        /* And we can leave. */
                        return;
                }
        }
}

/* Files. */

static int
_pkg_freebsd_sort_files(struct pkg_property *p1, struct pkg_property *p2)
{
        (void)p1;
        (void)p2;
        return (0);
}

DEFINE_LIST_GET_NEXT_FUNCTION(pkg_freebsd_get_next_file, FBSD_FILES, pkg_file,
        _pkg_freebsd_sort_files);

/*
 * Creates a file in package ps Plist.  Passing in an _allocated_ pkg_file,
 * which will act as the interface to this node in the Plist.
 * 
 * Returns 0 on success, (-1) on failure.
 */
int
pkg_freebsd_file_create(struct pkg *p, struct pkg_file *f)
{
        struct pkg_property *pl;
        struct pkg_property *files;
        struct pkg_property *new;

        pl = pkg_get_property_list(p);
        if (pl == NULL) {
                return (-1);
        }

        if ((files = pkg_property_dict_get_property(pl, FBSD_FILES)) == NULL) {
                files = pkg_property_list_create(pl, FBSD_FILES);
                if (files == NULL) {
                        return (-1);
                }
        }
        /* Should probably check property_type here. */
        new = pkg_property_dict_create(files, FBSD_FILE);
        if (new == NULL) {
                return (-1);
        }
        f->plnode = new;

        return (0);
}

DEFINE_DICT_GET_STRING_FUNCTION(pkg_freebsd_file_get_pathname, FBSD_PATHNAME, 
        pkg_file);

int
pkg_freebsd_file_set_pathname(struct pkg_file *f, const char *pathname)
{
        struct pkg_property *pn;

        if (f->plnode == NULL) {
                return (-1);
        }

        if ((pn = pkg_property_dict_get_property(f->plnode, FBSD_PATHNAME)) == 
                NULL) {
                pn = pkg_property_string_create(f->plnode, FBSD_PATHNAME);
                if (pn == NULL) {
                        return (-1);
                }
        }
        
        if (pkg_property_string_set_string(pn, pathname) == NULL) {
                return (-1);
        }

        return (0);
}

DEFINE_DICT_GET_STRING_FUNCTION(pkg_freebsd_file_get_prefix, FBSD_PREFIX, 
        pkg_file);

int
pkg_freebsd_file_set_prefix(struct pkg_file *f, const char *prefix)
{
        (void)f;
        (void)prefix;
        return (0);
}

DEFINE_DICT_GET_STRING_FUNCTION(pkg_freebsd_file_get_hash, FBSD_HASH, pkg_file);

int
pkg_freebsd_file_set_hash(struct pkg_file *f, const char *hash)
{
        struct pkg_property *pn;

        if (f->plnode == NULL) {
                return (-1);
        }

        if ((pn = pkg_property_dict_get_property(f->plnode, FBSD_HASH)) == 
                NULL) {
                pn = pkg_property_string_create(f->plnode, FBSD_HASH);
                if (pn == NULL) {
                        return (-1);
                }
        }

        if (pkg_property_string_set_string(pn, hash) == NULL) {
                return (-1);
        }
        
        return (0);
}

int
pkg_freebsd_file_get_ignored(struct pkg_file *f)
{
        struct pkg_property *pn;

        if (f->plnode == NULL) {
                return (0);
        }

        pn = pkg_property_dict_get_property(f->plnode, FBSD_IGNORE);
        if (pn == NULL) {
                return (0);
        }

        return (pkg_property_boolean_get_boolean(pn));
}

/* Dependencies. */

static int
_pkg_freebsd_sort_dependencies(struct pkg_property *p1, struct pkg_property *p2)
{
        struct pkg_property *p1p;
        struct pkg_property *p2p;

        p1p = pkg_property_dict_get_property(p1, FBSD_NAME);
        p2p = pkg_property_dict_get_property(p2, FBSD_NAME);

        return (strcmp(pkg_property_string_get_string(p1p),
                        pkg_property_string_get_string(p2p)));
}

DEFINE_LIST_GET_NEXT_FUNCTION(pkg_freebsd_get_next_dependency,
        FBSD_DEPENDENCIES, pkg_dependency, _pkg_freebsd_sort_dependencies);

DEFINE_DICT_GET_STRING_FUNCTION(pkg_freebsd_dependency_get_name,
        FBSD_NAME, pkg_dependency);

DEFINE_DICT_GET_STRING_FUNCTION(pkg_freebsd_dependency_get_origin,
        FBSD_ORIGIN, pkg_dependency);

/* Conflicts. */

static int
_pkg_freebsd_sort_conflicts(struct pkg_property *p1, struct pkg_property *p2)
{
        (void)p1;
        (void)p2;
        return (0);
}

DEFINE_LIST_GET_NEXT_FUNCTION(pkg_freebsd_get_next_conflict, FBSD_CONFLICTS, 
        pkg_conflict, _pkg_freebsd_sort_conflicts);

DEFINE_CREATE_IN_LIST_FUNCTION(pkg_freebsd_conflict_create, FBSD_CONFLICTS,
        FBSD_CONFLICT, pkg_conflict);

const char *
pkg_freebsd_conflict_get_expression(struct pkg_conflict *c)
{
        if (c->plnode == NULL) {
                return (NULL);
        }
        if (pkg_property_get_type(c->plnode) != PLSTRING) {
                PKG_CLIENT_CRASH(__func__, "Node is not a PLSTRING");
        }
        return (pkg_property_string_get_string(c->plnode));
}

/* Exec/Unexec. */

DEFINE_LIST_GET_NEXT_FUNCTION(pkg_freebsd_get_next_unexec, FBSD_UNEXECS,
        pkg_exec, NULL);

DEFINE_DICT_GET_STRING_FUNCTION(pkg_freebsd_exec_get_command, FBSD_COMMAND,
        pkg_exec);


/* Scripts. */

#define DEFINE_GET_SCRIPT_FUNCTION(fnname, scriptname)                         \
const char *                                                                   \
fnname(struct pkg *p)                                                          \
{                                                                              \
        return (pkg_get_script_pathname(p, scriptname));                       \
}

DEFINE_GET_SCRIPT_FUNCTION(pkg_freebsd_get_install_script_path,
        FBSD_METANAME_PRE_INSTALL);
DEFINE_GET_SCRIPT_FUNCTION(pkg_freebsd_get_post_install_script_path,
        FBSD_METANAME_POST_INSTALL);
DEFINE_GET_SCRIPT_FUNCTION(pkg_freebsd_get_deinstall_script_path,
        FBSD_METANAME_PRE_DEINSTALL);
DEFINE_GET_SCRIPT_FUNCTION(pkg_freebsd_get_post_deinstall_script_path,
        FBSD_METANAME_POST_DEINSTALL);
DEFINE_GET_SCRIPT_FUNCTION(pkg_freebsd_get_require_script_path,
        FBSD_METANAME_REQUIRE);

char *
pkg_freebsd_run_command(const char *fmt, ...)
{
        va_list ap;
        va_start(ap, fmt);
        return (NULL);
}

int
pkg_freebsd_extract(struct pkg *p, struct archive *a, struct archive_entry *ae)
{
        (void)p;
        (void)a;
        (void)ae;
        struct pkg_file f;
        const char *pathname;
        const char *prefix;
        char extractpath[PATH_MAX];

        pathname = archive_entry_pathname(ae);
        while (pkg_freebsd_get_next_file(p, &f) == PKG_OK) {
                if (pkg_freebsd_file_get_ignored(&f) == 0 && 
                        strcmp(pathname, pkg_freebsd_file_get_pathname(&f)) == 
                        0) {
                        prefix = pkg_freebsd_file_get_prefix(&f);
                        strcpy(extractpath, (prefix != NULL ? prefix : ""));
                        strcat(extractpath, pathname);
                        archive_entry_set_pathname(ae, extractpath);
                }
        }

        return (0);
}

#if 0
/* Database. */
int
pkg_freebsd_database_install_pkg(struct pkg_database *db, FILE *fp, 
        struct pkg *p)
{
        struct archive *a;
        struct archive_entry *e;
        int r;

        a = archive_read_new();
        e = archive_entry_new();

        archive_read_support_tar(a);
        archive_read_support_compression_gzip(a);
        archive_read_support_compression_bzip2(a);

        archive_open_read_FILE(a, fp);

        while ((r = archive_read_next_entry(a, &e)) == ARCHIVE_OK) {
                if (pkg_meta(p, archive_entry_pathname(e))) {
                } else {
                        /* rebuild pathname and extract into place. */
                }
        }
        return (PKG_NOT_OK);
}
#endif
